﻿/*  ----------------------------------------------------------------------------
 *  DES-Release 2
 *  ----------------------------------------------------------------------------
 *  File:       Forwarder.cs
 *  Author:     nitinkoshy@gmail.com
 *  Source:     http://des.codeplex.com/
 *  ----------------------------------------------------------------------------
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ServiceModel;
using System.Configuration;
using System.Messaging;
using System.Diagnostics;


namespace DES.Server.Service.Workers
{
    public class Forwarder : WorkerBase
    {

        public override void PerformTask(DESServerService serverService)
        {
            ServerTask serverTask;
            if (serverService.ProcessedTasks.Count == 0)
                return;

            Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
            Trace.WriteLine(String.Format("Forwarder Message : Processed queue count : {0}", serverService.ProcessedTasks.Count));

            lock (serverService.ProcessedTasks)
            {
                serverTask = serverService.ProcessedTasks.Dequeue();
            }

            if (CollectionGateKeeper.ShouldTaskWait(serverTask, serverService))
            {
                lock (serverService.CollectionGate)
                {
                    serverService.CollectionGate.Add(serverTask);
                }
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Forwarder Message : task should wait : {0}", serverTask.Task.Name));
                return;
            }
            else
                CollectionGateKeeper.ClearChildTasksFromGate(serverTask, serverService); //do a final cleanup 

            if (CanReturnTaskToClient(serverTask, serverService))
            {
                ReturnTaskToClient(serverTask);
                return;
            }

            MoveTaskToNextServer(serverTask, serverService);
        }

        private static void ReturnTaskToClient(ServerTask serverTask)
        {
            Trace.Write(String.Format("[{0}]:", DateTime.Now.ToShortTimeString()));
            Trace.WriteLine(String.Format("Forwarder Message : I am the root for task : {0}", serverTask.Task.Name));
            //client has a endpoint?
            if (serverTask.Task.EndPoint != String.Empty)
            {
                //for the moment the endpoint is assumed to be a MSMQ queue
                //tomorrow it could be a SOAP/WCF/REST endpoint etc
                try
                {
                    Trace.Write(String.Format("[{0}]:", DateTime.Now.ToShortTimeString()));
                    Trace.WriteLine(String.Format("Forwarder Message : Storing result to msmq : {0}", serverTask.Task.Name));
                    MessageQueue msgQ = new MessageQueue(serverTask.Task.EndPoint);
                    Message msg = new Message();
                    msg.Body = serverTask.Task;
                    msgQ.Send(msg, serverTask.Task.Id.ToString());
                }
                catch (Exception E)
                {
                    Trace.Write(String.Format("[{0}]:", DateTime.Now.ToShortTimeString()));
                    Trace.WriteLine(String.Format("Forwarder Message : Error while storing : {0}:{1}", serverTask.Task.Name, E.Message));
                }

            }//has end point
        }

        /// <summary>
        /// moves task to the next server. next server could be a server
        /// where the task needs to wait for its sibling/parent, it could be the root server,
        /// or the immediate parent server
        /// </summary>
        /// <param name="serverTask"></param>
        /// <param name="serverService"></param>
        private void MoveTaskToNextServer(ServerTask serverTask, DESServerService serverService)
        {
            Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
            Trace.WriteLine(String.Format("Forwarder Message : Trying to move the task {0} up the call tree", serverTask.Task.Name));
            //check if the task root is directly accessible from here
            DESServerInfo nextServer = null;

            //check if the task needs to get to the server where the tasks got split
            if ((serverTask.ChildTasksMarkedAtServer != null))
                if (!serverTask.ChildTasksCollected)
                {
                    nextServer = IsDirectlyConnectedToServer(serverTask.ChildTasksMarkedAtServer.Id, serverService);
                    if (nextServer == null)
                        nextServer = serverTask.ImmediateParentServer;//have to slowly move up the tree to the waiting server
                }

            //check if the root is directly connected
            if (nextServer == null)
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Forwarder Message : ChildTasksMarkedAtServer IS NULL for {0}", serverTask.Task.Name));
                nextServer = IsDirectlyConnectedToServer(serverTask.RootServer.Id, serverService);
            }

            //just get the immediate parent server
            if (nextServer == null)
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Forwarder Message : No direct connect to root {0} for {1}", serverTask.RootServer.Id, serverTask.Task.Name));
                nextServer = serverTask.ImmediateParentServer;
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Forwarder Message : Root server NOT directly connected"));
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Forwarder Message : Moving up the call tree to {0}", nextServer.Id));
            }

            if (nextServer == null)
                throw new ApplicationException("Next Server Empty while trying to forward");

            ChannelFactory<IDESServerService> desServerServiceFactory = new ChannelFactory<IDESServerService>(
                        new NetTcpBinding(),
                        new EndpointAddress(nextServer.EndPoint));
            IDESServerService desServerService = desServerServiceFactory.CreateChannel();


            try
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Forwarder Message : calling the next parent for task {0}", serverTask.Task.Name));
                desServerService.SetProcessedTask(serverTask, serverService.ServerInfo);
                desServerServiceFactory.Close();
                Trace.WriteLine(String.Format("Forwarder Message : called SetProcessedTask() for {0} from Server {1} to {2}", serverTask.Task.Name, serverService.ServerInfo.Id, nextServer.Id));

            }
            catch (EndpointNotFoundException exception)//this server is not connected
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Forwarder Message : could not move task {0} up the tree, enqueue back. Reason {1}", serverTask.Task.Name, exception.Message));

                lock (serverService.ProcessedTasks)
                {
                    serverService.ProcessedTasks.Enqueue(serverTask); //put the task back
                }
            }
        }

        public override string GetWorkerName()
 
        {
            return "Forwarder";
        }

        public override int GetThreadSleepTime()
        {
            return Convert.ToInt32(ConfigurationManager.AppSettings["forwarderthreadsleeptime"]);
        }


        private bool CanReturnTaskToClient(ServerTask serverTask, DESServerService serverService)
        {
            return (serverTask.HasReachedRoot(serverService) && (!CollectionGateKeeper.ShouldTaskWait(serverTask, serverService)));
        }


        public DESServerInfo IsDirectlyConnectedToServer(string serverId, DESServerService serverService)
        {
            foreach (DESServerInfo server in serverService.ConnectedServers)
                if (server.Id == serverId)
                    return server;

            return null;
        }
    }
}
